/*
Copyright 2021 Flant JSC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package template_tests

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/deckhouse/deckhouse/testing/helm"
)

var _ = Describe("Module :: user-authn :: helm template :: dex authenticator", func() {
	hec := SetupHelmConfig("")

	BeforeEach(func() {
		hec.ValuesSet("global.discovery.kubernetesVersion", "1.15.6")
		hec.ValuesSet("global.modules.publicDomainTemplate", "%s.example.com")
		hec.ValuesSet("global.modules.https.mode", "CertManager")
		hec.ValuesSet("global.modules.https.certManager.clusterIssuerName", "letsencrypt")
		hec.ValuesSet("global.modulesImages.registry.base", "registry.example.com")
		hec.ValuesSet("global.enabledModules", []string{"cert-manager", "vertical-pod-autoscaler"})
		hec.ValuesSet("global.discovery.d8SpecificNodeCountByRole.system", 2)
		hec.ValuesSet("global.discovery.kubernetesCA", "plainstring")

		hec.ValuesSet("userAuthn.internal.kubernetesDexClientAppSecret", "plainstring")
		hec.ValuesSet("userAuthn.internal.dexTLS.crt", "plainstring")
		hec.ValuesSet("userAuthn.internal.dexTLS.key", "plainstring")
	})
	Context("With DexAuthenticator object", func() {
		BeforeEach(func() {
			hec.ValuesSetFromYaml("userAuthn.internal.dexAuthenticatorCRDs", `
- name: test
  encodedName: justForTest
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  spec:
    applications:
    - domain: authenticator.example.com
      ingressClassName: test
      ingressSecretName: test
      whitelistSourceRanges:
      - 1.1.1.1
      - 192.168.0.0/24
    - domain: authenticator-two.example.com
      ingressClassName: test-two
      ingressSecretName: test
    sendAuthorizationHeader: true
    keepUsersLoggedInFor: "1020h"
    allowedGroups:
    - everyone
    - admins
    allowedEmails:
    - test@mail.io
    nodeSelector:
      testnode: ""
    tolerations:
    - key: foo
      operator: Equal
      value: bar
- name: test-2
  encodedName: justForTest2
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  allowAccessToKubernetes: true
  spec:
    applications:
    - domain: authenticator.com
      ingressClassName: test
      ingressSecretName: test
    - domain: authenticator-two.com
      ingressClassName: test-two
      ingressSecretName: test
    sendAuthorizationHeader: false
- name: test-3
  encodedName: justForTest3
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  allowAccessToKubernetes: true
  spec:
    keepUsersLoggedInFor: "19m"
- name: test-4
  encodedName: justForTest4
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  allowAccessToKubernetes: true
  spec:
    keepUsersLoggedInFor: "2h20m4s"
`)
			hec.ValuesSet("userAuthn.idTokenTTL", "2h20m4s")

			// Since template tests do not run Go hooks, we must mock the values the hook would have created.
			hec.ValuesSetFromYaml("userAuthn.internal.dexAuthenticatorNames", `
"test-2@d8-test":
  name: "test-2-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-2"
  secretTruncated: false
  secretHash: ""
  ingressNames:
    "0":
      name: "test-2-dex-authenticator"
      truncated: false
      hash: ""
    "1":
      name: "test-2-3230e1af-dex-authenticator"
      truncated: false
      hash: ""
"test-3@d8-test":
  name: "test-3-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-3"
  secretTruncated: false
  secretHash: ""
"test-4@d8-test":
  name: "test-4-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-4"
  secretTruncated: false
  secretHash: ""
"test@d8-test":
  name: "test-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test"
  secretTruncated: false
  secretHash: ""
  ingressNames:
    "0":
      name: "test-dex-authenticator"
      truncated: false
      hash: ""
    "1":
      name: "test-05f0e90e-dex-authenticator"
      truncated: false
      hash: ""
`)
			hec.HelmRender()
		})
		It("Should create desired objects", func() {
			svc := hec.KubernetesResource("Service", "d8-test", "test-dex-authenticator")
			Expect(svc.Exists()).To(BeTrue())
			Expect(svc.Field("metadata.labels.deckhouse\\.io/dex-authenticator-for").String()).To(Equal("test"))
			Expect(svc.Field("metadata.labels.deckhouse\\.io/name-truncated").Exists()).To(BeFalse())

			Expect(hec.KubernetesResource("PodDisruptionBudget", "d8-test", "test-dex-authenticator").Exists()).To(BeTrue())
			Expect(hec.KubernetesResource("VerticalPodAutoscaler", "d8-test", "test-dex-authenticator").Exists()).To(BeTrue())
			Expect(hec.KubernetesResource("Secret", "d8-test", "registry-dex-authenticator").Exists()).To(BeTrue())

			secret := hec.KubernetesResource("Secret", "d8-test", "dex-authenticator-test")
			Expect(secret.Exists()).To(BeTrue())
			Expect(secret.Field("data.client-secret").String()).To(Equal("ZGV4U2VjcmV0"))
			Expect(secret.Field("data.cookie-secret").String()).To(Equal("Y29va2llU2VjcmV0"))

			oauth2clientTest := hec.KubernetesResource("OAuth2Client", "d8-user-authn", "justForTest")
			Expect(oauth2clientTest.Exists()).To(BeTrue())
			Expect(oauth2clientTest.Field("redirectURIs").String()).To(MatchJSON(`["https://authenticator.example.com/dex-authenticator/callback","https://authenticator-two.example.com/dex-authenticator/callback"]`))
			Expect(oauth2clientTest.Field("secret").String()).To(Equal("dexSecret"))
			Expect(oauth2clientTest.Field("allowedEmails").String()).To(MatchJSON(`["test@mail.io"]`))
			Expect(oauth2clientTest.Field("allowedGroups").String()).To(MatchJSON(`["everyone","admins"]`))

			ingressTest := hec.KubernetesResource("Ingress", "d8-test", "test-dex-authenticator")
			Expect(ingressTest.Exists()).To(BeTrue())
			Expect(ingressTest.Field("spec.ingressClassName").String()).To(Equal("test"))
			Expect(ingressTest.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/proxy-buffer-size").String()).To(Equal("32k"))
			Expect(ingressTest.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range").String()).To(Equal("1.1.1.1,192.168.0.0/24"))

			Expect(ingressTest.Field("spec.tls.0.hosts").String()).To(MatchJSON(`["authenticator.example.com"]`))
			Expect(ingressTest.Field("spec.tls.0.secretName").String()).To(Equal("test"))

			ingressTestTwo := hec.KubernetesResource("Ingress", "d8-test", "test-05f0e90e-dex-authenticator")
			Expect(ingressTestTwo.Exists()).To(BeTrue())
			Expect(ingressTestTwo.Field("spec.ingressClassName").String()).To(Equal("test-two"))
			Expect(ingressTestTwo.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/proxy-buffer-size").String()).To(Equal("32k"))
			Expect(ingressTestTwo.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range").Exists()).To(BeFalse())
			Expect(ingressTestTwo.Field("spec.tls.0.hosts").String()).To(MatchJSON(`["authenticator-two.example.com"]`))
			Expect(ingressTestTwo.Field("spec.tls.0.secretName").String()).To(Equal("test"))

			deploymentTest := hec.KubernetesResource("Deployment", "d8-test", "test-dex-authenticator")
			Expect(deploymentTest.Exists()).To(BeTrue())
			Expect(deploymentTest.Field("spec.template.spec.nodeSelector").String()).To(MatchJSON(`{"testnode": ""}`))
			Expect(deploymentTest.Field("spec.template.spec.tolerations").String()).To(MatchYAML(`
- key: foo
  operator: Equal
  value: "bar"
`))

			var oauth2proxyArgTest []string
			for _, result := range deploymentTest.Field("spec.template.spec.containers.0.args").Array() {
				oauth2proxyArgTest = append(oauth2proxyArgTest, result.String())
			}

			Expect(oauth2proxyArgTest).Should(ContainElement("--client-id=test-d8-test-dex-authenticator"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--oidc-issuer-url=https://dex.example.com/"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--redirect-url=/dex-authenticator/callback"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--set-authorization-header=true"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--cookie-expire=1020h"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--cookie-refresh=2h20m4s"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--whitelist-domain=authenticator.example.com"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--whitelist-domain=authenticator-two.example.com"))
			Expect(oauth2proxyArgTest).Should(ContainElement("--scope=groups email openid profile offline_access"))

			oauth2client2 := hec.KubernetesResource("OAuth2Client", "d8-user-authn", "justForTest2")
			Expect(oauth2client2.Exists()).To(BeTrue())
			Expect(oauth2client2.Field("redirectURIs").String()).To(MatchJSON(`["https://authenticator.com/dex-authenticator/callback","https://authenticator-two.com/dex-authenticator/callback"]`))
			Expect(oauth2client2.Field("secret").String()).To(Equal("dexSecret"))

			ingressTest2 := hec.KubernetesResource("Ingress", "d8-test", "test-2-dex-authenticator")
			Expect(ingressTest2.Exists()).To(BeTrue())
			Expect(ingressTest2.Field("spec.ingressClassName").String()).To(Equal("test"))
			Expect(ingressTest2.Field("metadata.labels.deckhouse\\.io/dex-authenticator-for").String()).To(Equal("test-2"))
			Expect(ingressTest2.Field("metadata.labels.deckhouse\\.io/name-truncated").Exists()).To(BeFalse())

			Expect(ingressTest2.Field("spec.tls.0.hosts").String()).To(MatchJSON(`["authenticator.com"]`))
			Expect(ingressTest2.Field("spec.tls.0.secretName").String()).To(Equal("test"))
			Expect(ingressTest2.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/proxy-buffer-size").Exists()).To(BeFalse())
			Expect(ingressTest2.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range").Exists()).To(BeFalse())

			ingressTest2Two := hec.KubernetesResource("Ingress", "d8-test", "test-2-3230e1af-dex-authenticator")
			Expect(ingressTest2Two.Exists()).To(BeTrue())
			Expect(ingressTest2Two.Field("spec.ingressClassName").String()).To(Equal("test-two"))
			Expect(ingressTest2Two.Field("metadata.labels.deckhouse\\.io/dex-authenticator-for").String()).To(Equal("test-2"))
			Expect(ingressTest2Two.Field("metadata.labels.deckhouse\\.io/name-truncated").Exists()).To(BeFalse())

			Expect(ingressTest2Two.Field("spec.tls.0.hosts").String()).To(MatchJSON(`["authenticator-two.com"]`))
			Expect(ingressTest2Two.Field("spec.tls.0.secretName").String()).To(Equal("test"))
			Expect(ingressTest2Two.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/proxy-buffer-size").Exists()).To(BeFalse())
			Expect(ingressTest2Two.Field("metadata.annotations.nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range").Exists()).To(BeFalse())

			deploymentTest2 := hec.KubernetesResource("Deployment", "d8-test", "test-2-dex-authenticator")
			Expect(deploymentTest2.Exists()).To(BeTrue())
			Expect(deploymentTest2.Field("spec.template.spec.nodeSelector").String()).To(MatchJSON(`{"node-role.deckhouse.io/system": ""}`))
			Expect(deploymentTest2.Field("spec.template.spec.tolerations").Exists()).To(BeTrue()) // default taints

			var oauth2proxyArgTest2 []string
			for _, result := range deploymentTest2.Field("spec.template.spec.containers.0.args").Array() {
				oauth2proxyArgTest2 = append(oauth2proxyArgTest2, result.String())
			}

			Expect(oauth2proxyArgTest2).Should(ContainElement("--client-id=test-2-d8-test-dex-authenticator"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--oidc-issuer-url=https://dex.example.com/"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--redirect-url=/dex-authenticator/callback"))
			Expect(oauth2proxyArgTest2).ShouldNot(ContainElement("--set-authorization-header=true"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--cookie-expire=168h"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--cookie-refresh=2h20m4s"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--whitelist-domain=authenticator.com"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--whitelist-domain=authenticator-two.com"))
			Expect(oauth2proxyArgTest2).Should(ContainElement("--scope=groups email openid profile offline_access federated:id audience:server:client_id:kubernetes"))

			deploymentTest3 := hec.KubernetesResource("Deployment", "d8-test", "test-3-dex-authenticator")
			Expect(deploymentTest3.Exists()).To(BeTrue())
			var oauth2proxyArgTest3 []string
			for _, result := range deploymentTest3.Field("spec.template.spec.containers.0.args").Array() {
				oauth2proxyArgTest3 = append(oauth2proxyArgTest3, result.String())
			}
			Expect(oauth2proxyArgTest3).Should(ContainElement("--cookie-expire=2h20m5s"))
			Expect(oauth2proxyArgTest3).Should(ContainElement("--cookie-refresh=2h20m4s"))

			deploymentTest4 := hec.KubernetesResource("Deployment", "d8-test", "test-4-dex-authenticator")
			Expect(deploymentTest4.Exists()).To(BeTrue())
			var oauth2proxyArgTest4 []string
			for _, result := range deploymentTest4.Field("spec.template.spec.containers.0.args").Array() {
				oauth2proxyArgTest4 = append(oauth2proxyArgTest3, result.String())
			}
			Expect(oauth2proxyArgTest4).Should(ContainElement("--cookie-expire=2h20m5s"))
			Expect(oauth2proxyArgTest4).Should(ContainElement("--cookie-refresh=2h20m4s"))
		})
	})

	Context("With DexAuthenticator resources", func() {
		BeforeEach(func() {
			hec.ValuesSetFromYaml("userAuthn.internal.dexAuthenticatorCRDs", `
- name: test-with-resources
  encodedName: testWithResources
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  spec:
    applications:
    - domain: test-with-resources.example.com
      ingressClassName: test
      ingressSecretName: test
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        cpu: "200m"
        memory: "256Mi"
- name: test-with-resources-both
  encodedName: testWithResourcesBoth
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  spec:
    applications:
    - domain: test-with-resources-both.example.com
      ingressClassName: test
      ingressSecretName: test
    resources:
      requests:
        cpu: "150m"
        memory: "192Mi"
      limits:
        cpu: "300m"
        memory: "384Mi"
      redis:
        requests:
          cpu: "50m"
          memory: "64Mi"
        limits:
          cpu: "100m"
          memory: "128Mi"
- name: test-without-resources
  encodedName: testWithoutResources
  namespace: d8-test
  credentials:
    appDexSecret: dexSecret
    cookieSecret: cookieSecret
  spec:
    applications:
    - domain: test-without-resources.example.com
      ingressClassName: test
      ingressSecretName: test
`)
			hec.ValuesSetFromYaml("userAuthn.internal.dexAuthenticatorNames", `
"test-with-resources@d8-test":
  name: "test-with-resources-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-with-resources"
  secretTruncated: false
  secretHash: ""
  ingressNames:
    "0":
      name: "test-with-resources-dex-authenticator"
      truncated: false
      hash: ""
"test-with-resources-both@d8-test":
  name: "test-with-resources-both-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-with-resources-both"
  secretTruncated: false
  secretHash: ""
  ingressNames:
    "0":
      name: "test-with-resources-both-dex-authenticator"
      truncated: false
      hash: ""
"test-without-resources@d8-test":
  name: "test-without-resources-dex-authenticator"
  truncated: false
  hash: ""
  secretName: "dex-authenticator-test-without-resources"
  secretTruncated: false
  secretHash: ""
  ingressNames:
    "0":
      name: "test-without-resources-dex-authenticator"
      truncated: false
      hash: ""
`)
			hec.HelmRender()
		})

		It("Should disable VPA when resources specified", func() {
			// VPA should NOT exist when resources are specified
			Expect(hec.KubernetesResource("VerticalPodAutoscaler", "d8-test", "test-with-resources-dex-authenticator").Exists()).To(BeFalse())
			Expect(hec.KubernetesResource("VerticalPodAutoscaler", "d8-test", "test-with-resources-both-dex-authenticator").Exists()).To(BeFalse())

			// VPA should exist when resources are NOT specified
			Expect(hec.KubernetesResource("VerticalPodAutoscaler", "d8-test", "test-without-resources-dex-authenticator").Exists()).To(BeTrue())
		})

		It("Should apply resources to dex-authenticator container", func() {
			deployment := hec.KubernetesResource("Deployment", "d8-test", "test-with-resources-dex-authenticator")
			Expect(deployment.Exists()).To(BeTrue())

			// Check dex-authenticator container resources
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.cpu").String()).To(Equal("100m"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.memory").String()).To(Equal("128Mi"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.limits.cpu").String()).To(Equal("200m"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.limits.memory").String()).To(Equal("256Mi"))
		})

		It("Should apply resources to redis container when specified", func() {
			deployment := hec.KubernetesResource("Deployment", "d8-test", "test-with-resources-both-dex-authenticator")
			Expect(deployment.Exists()).To(BeTrue())

			// Check dex-authenticator container resources
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.cpu").String()).To(Equal("150m"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.memory").String()).To(Equal("192Mi"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.limits.cpu").String()).To(Equal("300m"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.limits.memory").String()).To(Equal("384Mi"))

			// Check redis container resources
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.cpu").String()).To(Equal("50m"))
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.memory").String()).To(Equal("64Mi"))
			Expect(deployment.Field("spec.template.spec.containers.1.resources.limits.cpu").String()).To(Equal("100m"))
			Expect(deployment.Field("spec.template.spec.containers.1.resources.limits.memory").String()).To(Equal("128Mi"))
		})

		It("Should use default resources for redis when not specified", func() {
			deployment := hec.KubernetesResource("Deployment", "d8-test", "test-with-resources-dex-authenticator")
			Expect(deployment.Exists()).To(BeTrue())

			// Check redis container resources (should not have CPU/Memory when VPA enabled)
			// CPU/Memory should not be set when VPA is enabled and redis resources not specified
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.cpu").Exists()).To(BeFalse())
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.memory").Exists()).To(BeFalse())
		})

		It("Should use default resources when VPA disabled and resources not specified", func() {
			hec.ValuesSet("global.enabledModules", []string{"cert-manager"})
			hec.HelmRender()

			deployment := hec.KubernetesResource("Deployment", "d8-test", "test-without-resources-dex-authenticator")
			Expect(deployment.Exists()).To(BeTrue())

			// Check dex-authenticator container resources
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.cpu").String()).To(Equal("10m"))
			Expect(deployment.Field("spec.template.spec.containers.0.resources.requests.memory").String()).To(Equal("25Mi"))

			// Check redis container resources
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.cpu").String()).To(Equal("10m"))
			Expect(deployment.Field("spec.template.spec.containers.1.resources.requests.memory").String()).To(Equal("25Mi"))
		})
	})
})
